The R package iconr is grounded in graph theory and spatial analysis. It offers concepts and functions for modeling Prehistoric iconographic compositions and for preparing for further analysis (clustering, Harris diagram, etc.) in order to contribute to cross-cultural iconography comparison studies through a greater normalization of quantitative analysis (Alexander 2008; Huet and Alexander 2015; Huet 2018).

Decoration graphs

The main principle of the iconr package is to consider any iconographic composition (here, ‘decoration’) as a geometric graph of graphical units (GUs). This geometric graph is also known as a planar graph or spatialised graph. The GUs are decorated surfaces (POLYGONS) modeled as nodes (POINTS). When these GUs are main nodes, and not attribute nodes, they share edges (LINES) with one another when their Voronoi cells share a border (birel: touches).  

GIS view of the Cerro Muriano 1 stelae with the Voronoi cells of its GUs, GUs (main nodes) and proximity links between these GUs (edges)

GIS view of the Cerro Muriano 1 stelae with the Voronoi cells of its GUs, GUs (main nodes) and proximity links between these GUs (edges)

 

Graph theory offers a conceptual framework and indices (global at the entire graph scale, local at the vertex scale) to deal with notions of networks, relationships and neighbourhoods. The graph is commonly built within a GIS interface. Indeed, use of GIS allows one to create a spatial database of the decoration’s iconographic contents and facilitates data recording and visualization. For example, snapping options can connect GUs (nodes) with lines (edges) and we can exploit tools such as feature symbology, layer transparency, etc.  

The latest development version of the iconr package and its vignette can be downloaded from GitHub

devtools::install_github("zoometh/iconr", build_vignettes=TRUE)

The R package iconr is composed of functions and a dataset example. The main R packages used by the iconr package are:

Load the package iconr

library(iconr)

Dataset

The training dataset comprises drawings with their listing and data for nodes, edges. The current path for the training dataset is the exdata/ folder. All the input data (dataframes, shapefiles, images) start with a letter (regex "^[[:alpha:]]") while the output data start with a punctuation symbol (regex "^[[:punct:]]") or a number (regex "^[[:digit:]]")

dataDir <- system.file("extdata", package = "iconr")
cat(grep("^[[:alpha:]]",list.files(dataDir),value=T), sep="\n")
> Brozas.Brozas.jpg
> Cerro_Muriano.Cerro_Muriano_1.jpg
> edges.csv
> edges.dbf
> edges.shp
> edges.shx
> edges.tsv
> gis.png
> Ibahernando.Ibahernando.jpg
> imgs.csv
> imgs.tsv
> nodes.csv
> nodes.dbf
> nodes.shp
> nodes.shx
> nodes.tsv
> Torrejon_Rubio.Torrejon_Rubio_1.jpg
> Zarza_de_Montanchez.Zarza_de_Montanchez.jpg

Each decoration is identified by its name and the name of the site to which it belongs. Each decoration is a set of:

  • images (.jpg, .png, .jpeg, .tiff, .pdf, etc.)

  • decoration identifiers dataframe (.tsv or .csv)

imgs_path <- paste0(dataDir, "/imgs.csv")
imgs <- read.table(imgs_path, sep=";", stringsAsFactors = FALSE)
  • nodes and edges dataframe (.tsv, .csv or .shp)

A GIS interface is often the most practical way to record graph nodes and graph edges with POINTS and LINES geometries coming from shapefiles (.shp)

nodes_path <- paste0(dataDir, "/nodes.shp")
nodes.shp <- rgdal::readOGR(dsn = nodes_path, layer = "nodes", verbose = F)
nodes <- as.data.frame(nodes.shp)
edges_path <- paste0(dataDir, "/edges.shp")
edges.shp <- rgdal::readOGR(dsn = edges_path, layer = "edges", verbose = F)
edges <- as.data.frame(edges.shp)

Nodes and edges can also be recorded in tabular format: .tsv (tab-separated values) or .csv (comma or semicolon-separated values)

nodes_path <- paste0(dataDir, "/nodes.tsv")
edges_path <- paste0(dataDir, "/edges.tsv")
nodes <- read.table(nodes_path, sep="\t", stringsAsFactors = FALSE)
edges <- read.table(edges_path, sep="\t", stringsAsFactors = FALSE)

The list of graph decorations is created with the list_dec() function. These graphs are igraph objects

lgrph <- list_dec(imgs, nodes, edges)
g <- lgrph[[1]]
as.character(class(g))
> [1] "igraph"

By default, the plot.igraph() function (ie, igraph::plot()) spazialisation (layout) is based on x and y columns, when these exist. This is so in our case since we are working with geometric graphs. Very different layouts exist for the same graph (graph drawing)

par(mfrow=c(1, 2))
coords <- layout.fruchterman.reingold(lgrph[[1]])
plot(g,
     vertex.size = 15,
     vertex.frame.color="white",
     vertex.label.family = "sans",
     vertex.label.cex = .7,
)
title("graph drawing based on x, y coordinates", cex.main=.8, font.main= 1)
plot(g,
     layout = coords,
     vertex.size = 15 + (degree(g)*10),
     vertex.frame.color="white",
     vertex.label.family = "sans",
     vertex.label.cex = .7,
)
title(paste0("force-directed graph drawing,",
             " \n size of nodes depending on their degree"),
      cex.main=.8, font.main= 1)
mtext(lgrph[[1]]$decor, cex = 1, side = 3, line = -20, outer = TRUE)

 

Images

Image or drawing (eg, images, rasters, grids) formats accepted are the common ones (jpg, png, jpeg, tiff, pdf, etc.). The images in the current training dataset come from a PhD thesis, published by M. Diaz-Guardamino (Dı́az-Guardamino Uribe 2010):

imgs_path <- paste0(dataDir, "/imgs.tsv")
imgs <- read.table(imgs_path, sep="\t", stringsAsFactors = FALSE)
knitr::kable(imgs, "html") %>% 
  kableExtra::kable_styling(full_width = FALSE, position = "center", font_size=12)
idf site decor img
1 Cerro Muriano Cerro Muriano 1 Cerro_Muriano.Cerro_Muriano_1.jpg
2 Torrejon Rubio Torrejon Rubio 1 Torrejon_Rubio.Torrejon_Rubio_1.jpg
3 Brozas Brozas Brozas.Brozas.jpg
4 Zarza de Montanchez Zarza De Montanchez Zarza_de_Montanchez.Zarza_De_Montanchez.jpg
5 Ibahernando Ibahernando Ibahernando.Ibahernando.jpg

 

The decoration’s unique identifiers are the concatenation of the site name and the decoration name. For example, the name of the Cerrano Muriano 1 decoration is Cerro_Muriano.Cerro_Muriano_1.jpg. For a given decoration, its image is the reference space of the graph: nodes and edges inherit their coordinates from the image grid. Identifiers and paths to images are stored in the imgs dataframe:

library(magick)
dataDir <- paste0(system.file("extdata", package = "iconr"))
imgs_path <- paste0(dataDir, "/imgs.csv")
imgs <- read.table(imgs_path, sep=";", stringsAsFactors = FALSE)
cm1 <- image_read(paste0(dataDir, "/", imgs[1,"img"]))
W <- as.character(image_info(cm1)$width)
H <- as.character(image_info(cm1)$height)
o <- "0"
image_border(image = cm1, "#808080", "2x2") %>%
  image_annotate(text = paste0(o, ", ", o),
                 size = 20,
                 gravity = "northwest") %>%
  image_annotate(text = paste0(W, ", ", o),
                 size = 20,
                 gravity = "northeast") %>%
  image_annotate(text = paste0(o, ", ", H),
                 size = 20,
                 gravity = "southwest") %>%
  image_annotate(text = paste0(W, ", ", H),
                 size = 20,
                 gravity = "southeast")
\label{fig:figs}Cerro_Muriano.Cerro_Muriano_1.jpg with the coordinates of its corners in 'px'

Cerro_Muriano.Cerro_Muriano_1.jpg with the coordinates of its corners in ‘px’

 

Nodes data

Nodes are stored in a dataframe (.csv or .tsv) or a shapefile (.shp). If the input data comes from a dataframe (.csv or .tsv), coordinate columns x and y are required:

nds.df <- read_nds(site = "Cerro Muriano", decor = "Cerro Muriano 1", doss = dataDir) 
knitr::kable(nds.df, "html") %>% 
  kableExtra::kable_styling(full_width = FALSE, position = "center", font_size=12)
site decor id type x y
Cerro Muriano Cerro Muriano 1 1 personnage 349.8148 -298.3244
Cerro Muriano Cerro Muriano 1 2 casque 349.8148 -243.9851
Cerro Muriano Cerro Muriano 1 3 lance 238.4637 -298.3244
Cerro Muriano Cerro Muriano 1 4 bouclier 446.0222 -381.1697
Cerro Muriano Cerro Muriano 1 5 peigne 283.0041 -358.0086
Cerro Muriano Cerro Muriano 1 7 sexe_masculin 342.6884 -427.4917
Cerro Muriano Cerro Muriano 1 8 lingot_pdb 451.1489 -237.4782

While, in theory, the nodes are the exact centroids of each GU. However, they can also be located manually near to the centroids.

column names

  • site: decoration site

  • decor: decoration name

  • id: id of the edges (a unique number)

  • type: type of node

  • x, y: coordinates of node

Edges data

Edges are stored in a dataframe (.csv or .tsv) or a shapefile (.shp).

edges <- read.table(edges_path, sep="\t", stringsAsFactors = FALSE)
knitr::kable(head(edges), "html") %>% 
  kableExtra::kable_styling(full_width = FALSE, position = "center", font_size=12) %>%
  gsub("\\+", "$$+$$", .)
site decor a b type
Cerro Muriano Cerro Muriano 1 1 4 =
Cerro Muriano Cerro Muriano 1 1 5 =
Cerro Muriano Cerro Muriano 1 3 5 =
Cerro Muriano Cerro Muriano 1 1 2 \[+\]
Cerro Muriano Cerro Muriano 1 1 7 \[+\]
Cerro Muriano Cerro Muriano 1 3 1 =

 

Edge identifiers are composed by their site, decor, a and b fields. For each decorattion (key: site and decor), their geometries are calculated or recalculated from node geometries with a join between the node id field and the a and b edge fields where a is the starting node of the edge (columns xa and ya) and b is the ending node of the edge (columns xb and yb):

eds.df <- read_eds(site = "Cerro Muriano", decor = "Cerro Muriano 1", doss = dataDir) 
knitr::kable(head(eds.df), "html") %>% 
  kableExtra::kable_styling(full_width = FALSE, position = "center", font_size=12) %>%
  gsub("\\+", "$$+$$", .)
site decor a b type xa ya xb yb
Cerro Muriano Cerro Muriano 1 1 4 = 349.8148 -298.3244 446.0222 -381.1697
Cerro Muriano Cerro Muriano 1 1 5 = 349.8148 -298.3244 283.0041 -358.0086
Cerro Muriano Cerro Muriano 1 3 5 = 238.4637 -298.3244 283.0041 -358.0086
Cerro Muriano Cerro Muriano 1 1 2 \[+\] 349.8148 -298.3244 349.8148 -243.9851
Cerro Muriano Cerro Muriano 1 1 7 \[+\] 349.8148 -298.3244 342.6884 -427.4917
Cerro Muriano Cerro Muriano 1 3 1 = 238.4637 -298.3244 349.8148 -298.3244

 

In a GIS and in any image matrix (raster, grid, etc.) the origin (0, 0) is the top-left corner and the x axes are always positive. But the y axis in a GIS shows always negative values while these y values are always positive in R. To overlap the nodes and the edges on the image, the plot functions calculate negative values of nodes and the edges y coordinates

column names

  • site : decoration site

  • decor : decoration name

  • id : id of the edge (a unique number)

  • a : id of the first node (see, nodes)

  • b : id of the second node (see, nodes)

  • type : edges types

    • = : normal edges between contemporaneous nodes (undirected edge)

    • + : attribute edges, between contemporaneous nodes where the node b is an attribute of node a (directed edge)

    • > : diachronic edges, between non-contemporaneous nodes where the node a overlaps node b, or node a is more ancient than node b (directed edge)

  • xa, ya: coordinates of the starting node, or main node, or overlapping node, or more recent node (a)

  • xb, yb: coordinates of the ending node, or attribute node, or overlapped node, , or more ancient node (b)

Edge types

Graph theory says that edges can be undirected or directed. In the iconr package, by default:

The named_elements() function allows one to display the textual notation of the different types of edges (-=-, -+- or ->-)

named_elements(lgrph[[1]], focus = "edges", nd.var="type")[1]      
> [1] "bouclier-=-personnage"

When there are nodes with the same nd.var, this function adds the suffix # to the nd.var in order to disambiguate the node list. This is the case, for example, for the chariot_char-+-cheval (x2) and chariot_char-+-roue (x2) edges of the Zarza de Montsanchez stelae (decoration 4)

sort(named_elements(lgrph[[4]], focus = "edges", nd.var="type"))
>  [1] "bouclier-=-chariot_char"   "bouclier-=-lance"         
>  [3] "bouclier-=-personnage"     "casque-+-rivet"           
>  [5] "casque-=-epee"             "casque-=-miroir"          
>  [7] "chariot_char-+-cheval"     "chariot_char-+-cheval#"   
>  [9] "chariot_char-+-roue"       "chariot_char-+-roue#"     
> [11] "chariot_char-=-personnage" "epee-=-lance"             
> [13] "epee-=-miroir"             "lance-=-personnage"
nds.df <- read_nds(site = "Zarza de Montanchez", 
                   decor = "Zarza De Montanchez",
                   doss = dataDir)
eds.df <- read_eds(site = "Zarza de Montanchez",
                   decor = "Zarza De Montanchez",
                   doss = dataDir)
img.graph <- plot_dec_grph(nodes = nds.df,
                           edges = eds.df,
                           site = "Zarza de Montanchez",
                           decor = "Zarza De Montanchez",
                           doss = dataDir,
                           nd.var = "type")

Employed with the basic R functions for loop (lapply()), count (table()) and order (order()), and removing the the suffix #, this function can be used to count the different types of edge. Here, we enumerate the most represented types:

all.edges <- lapply(lgrph, 
                    function(x) named_elements(x, focus = "edges",
                                               nd.var="type"))
edges.list <- gsub("#", "", unlist(all.edges))
all.edges.ct <- as.data.frame(table(edges.list))
all.edges.ct <- all.edges.ct[with(all.edges.ct, order(-Freq)), ] 
knitr::kable(head(all.edges.ct), row.names = F) %>%
  kableExtra::kable_styling(full_width = FALSE, position = "center", font_size=12)
edges.list Freq
chariot_char-+-cheval 4
chariot_char-+-roue 4
bouclier-=-epee 3
bouclier-=-lance 3
bouclier-=-chariot_char 2
bouclier-=-fibule 2

normal edges

The normal edges are undirected: 1-=-2 is equal to 2-=-1, node 1 and node 2 are two different main nodes. Different main nodes considerated to be contemporaneous and close to one another, may share an edge with the value = (textual notation: -=-) for their type. By convention, these edges are called normal and displayed as a plain line.

nds.df <- read_nds(site = "Brozas", 
                   decor = "Brozas",
                   doss = dataDir)
eds.df <- read_eds(site = "Brozas",
                   decor = "Brozas",
                   doss = dataDir)
img.graph.id <- plot_dec_grph(nodes = nds.df,
                              edges = eds.df,
                              site = "Brozas",
                              decor = "Brozas",
                              lbl.size = 2,
                              doss = dataDir)
img.graph.type <- plot_dec_grph(nodes = nds.df,
                                edges = eds.df,
                                site = "Brozas",
                                decor = "Brozas",
                                lbl.size = 2,
                                nd.var = 'type',
                                doss = dataDir)
magick::image_append(c(magick::image_read(img.graph.id),
                       magick::image_read(img.graph.type)))
Brozas stelae (decoration 3) with only *normal* edges: all the composition elements seem contemporaneous

Brozas stelae (decoration 3) with only normal edges: all the composition elements seem contemporaneous

 

For example, all the edges of the Brozas stelae are normal:

sort(named_elements(lgrph[[3]], focus = "edges", nd.var = "type"))
>  [1] "bouclier-=-epee"   "bouclier-=-fibule" "bouclier-=-lance" 
>  [4] "bouclier-=-miroir" "bouclier-=-peigne" "fibule-=-lance"   
>  [7] "fibule-=-miroir"   "fibule-=-peigne"   "lance-=-miroir"   
> [10] "lance-=-peigne"

attribute edges

When a node is an attribute of another, edges are identified with a + (textual notation: -+-) and displayed with a dashed line. The attribute edges are directed: 1-+-2 is not equal to 2-+-1, 1-+-2 means that node 1 is the main node and node 2 is one of its attribute nodes. For example, at the bottom of the Zarza de Montsanchez stelae (decoration 4), the main node 7 (chariot) is connected with four (4) attribute nodes:

  • two horses (cheval): 8 and 9

  • two wheels (roue): 10 and 11

nds.df <- read_nds(site = "Zarza de Montanchez", 
                   decor = "Zarza De Montanchez",
                   doss = dataDir)
eds.df <- read_eds(site = "Zarza de Montanchez",
                   decor = "Zarza De Montanchez",
                   doss = dataDir)
img.graph.id <- plot_dec_grph(nodes = nds.df,
                              edges = eds.df,
                              site = "Zarza de Montanchez",
                              decor = "Zarza De Montanchez",
                              ed.lwd = 2,
                              ed.color = c("darkorange"),
                              lbl.size = 1.5,
                              doss = dataDir)
img.graph.type <- plot_dec_grph(nodes = NULL,
                                edges = eds.df,
                                site = "Zarza de Montanchez",
                                decor = "Zarza De Montanchez",
                                ed.lwd = 2,
                                ed.color = c("darkorange"),
                                nd.var = 'type',
                                doss = dataDir)
magick::image_append(c(magick::image_read(img.graph.id),
                       magick::image_read(img.graph.type)))
Zarza De Montanchez stelae (decoration 4) showing *normal* and *attribute* edges

Zarza De Montanchez stelae (decoration 4) showing normal and attribute edges

 

diachronic edges

When a node overlaps another or is more recent than another, edges are identified with a > (textual notation: ->-) and displayed with a blue plain line. The diachronic edges are directed: 1->-2 is not equal to 2->-1, 1->-2 means that node 1 overlaps node 2, or node 1 is more recent than node 2. For example, the Ibahernando stelae has a latin inscription (écriture) overlapping a spear (lance) and a shield (bouclier).

nds.df <- read_nds(site = "Ibahernando", 
                   decor = "Ibahernando",
                   doss = dataDir)
eds.df <- read_eds(site = "Ibahernando",
                   decor = "Ibahernando",
                   doss = dataDir)
img.graph.id <- plot_dec_grph(nodes = nds.df,
                           edges = eds.df,
                           site = "Ibahernando",
                           decor = "Ibahernando",
                           lbl.size = 2,
                           doss = dataDir)
img.graph.type <- plot_dec_grph(nodes = nds.df,
                           edges = eds.df,
                           site = "Ibahernando",
                           decor = "Ibahernando",
                           nd.var = 'type',
                           lbl.size = 2,
                           doss = dataDir)
magick::image_append(c(magick::image_read(img.graph.id),
                       magick::image_read(img.graph.type)))
Ibahernando stelae (decoration 5) showing *normal* and *diachronic* edges

Ibahernando stelae (decoration 5) showing normal and diachronic edges

sort(named_elements(lgrph[[5]], focus = "edges", nd.var = "type"))
> [1] "bouclier-=-epee"     "bouclier-=-lance"    "ecriture->-bouclier"
> [4] "ecriture->-lance"

 

These overlappings, or diachronic iconographical layers, can be managed with the contemp_nds() function (see the section Contemporaneous contents)

Functions

Decoration graphs are constructed from nodes and edges. Graphs are 1-component: each decoration graph covers all the GUs of the decoration. The functions of the iconr package provide basic tools to manage these node data (.csv, .tsv or .shp) and edge data (.csv, .tsv or .shp) to create graphs, to plot and to compare them, to select contemporaneous GU compositions

cat(ls("package:iconr"), sep="\n")
> contemp_nds
> eds_compar
> labels_shadow
> list_compar
> list_dec
> named_elements
> nds_compar
> plot_compar
> plot_dec_grph
> read_eds
> read_nds
> same_elements

Plot

The graphical functions plot_dec_grph() and plot_compar() allow different choices for the color and size of the nodes, edges or labels. For example, the nodes and edges of Cerro Muriano 1, and the field type used for the label can be selected instead of the default id field (identifier of the node)

nds.df <- read_nds(site = "Cerro Muriano", decor = "Cerro Muriano 1", doss = dataDir)
eds.df <- read_eds(site = "Cerro Muriano", decor = "Cerro Muriano 1", doss = dataDir)
img.graph <- plot_dec_grph(nodes = nds.df,
                           edges = eds.df,
                           site = "Cerro Muriano",
                           decor = "Cerro Muriano 1",
                           nd.var = 'type',
                           lbl.size = 1.8,
                           doss = dataDir)
magick::image_read(img.graph)
Cerro Muriano 1 stelae (decoration 1) with the type of each GU

Cerro Muriano 1 stelae (decoration 1) with the type of each GU

 

A new field, long_cm, is added to the Cerro Muriano 1 nodes and the graph is replotted using this field instead of the type field, with brown colors and label bigger sizes

nds.df <- read_nds(site = "Cerro Muriano", decor = "Cerro Muriano 1", doss = dataDir)
nds.df$long_cm <- c(47, 9, 47, 18, 7, 3, 13)
eds.df <- read_eds(site = "Cerro Muriano", decor = "Cerro Muriano 1", doss = dataDir)
img.graph <- plot_dec_grph(nodes = nds.df,
                           edges = eds.df,
                           site = "Cerro Muriano",
                           decor = "Cerro Muriano 1",
                           nd.var = 'long_cm',
                           nd.color = "brown",
                           lbl.color = "brown",
                           ed.color = "brown",
                           lbl.size = 2.5,
                           doss = dataDir)
magick::image_read(img.graph)
Cerro Muriano 1 stelae (decoration 1) with the maximum length (in cm) of each GU

Cerro Muriano 1 stelae (decoration 1) with the maximum length (in cm) of each GU

 

Compare

Elements of the graphs (nodes and edges) can be compared across all graphs or between a pair of graphs with the same_elements() and plot_compar() functions

  • same_elements() function permits one to count common elements between n graphs

  • plot_compar() function shows a graphical output for these common elements

By default, in a pairwise comparison of decorations, common nodes and edges are displayed in red, but their colors – and other graphical parameters – can be modified. When not all GUs are contemporaneous with one another, the non-contemporaneous ones can be removed with the contemp_nds() function.

Node comparisons

A classic study in archaeological research is to count the common nodes between pairs of decorations. This can be done with the same_elements() function with a node focus (focus = "nodes"), and considering for example their type.

imgs_path <- paste0(dataDir, "/imgs.tsv")
nodes_path <- paste0(dataDir, "/nodes.tsv")
edges_path <- paste0(dataDir, "/edges.tsv")
imgs <- read.table(imgs_path, sep="\t", stringsAsFactors = FALSE)
nodes <- read.table(nodes_path, sep="\t", stringsAsFactors = FALSE)
edges <- read.table(edges_path, sep="\t", stringsAsFactors = FALSE)
lgrph <- list_dec(imgs, nodes, edges)
df.same_nodes <- same_elements(lgrph,
                               focus = "nodes",
                               nd.var = "type")
diag(df.same_nodes) <- cell_spec(diag(df.same_nodes),
                                 font_size = 9)
knitr::kable(df.same_nodes, row.names = TRUE, escape = F, table.attr = "style='width:30%;'",
             caption = "count of common nodes between decorations") %>%
  column_spec(1, bold=TRUE) %>%
  kableExtra::kable_styling(position = "center", font_size = 12)
count of common nodes between decorations
1 2 3 4 5
1 7 2 3 4 2
2 2 12 5 9 3
3 3 5 6 4 3
4 4 9 4 12 3
5 2 3 3 3 4

 

The result of same_elements() is a symetrical dataframe where row names and column headers are the identifiers of the decorations:

  • cells of the dataframe show the total number of common nodes by pairs of decorations

  • the diagonal of the dataframe shows the total number of nodes of a given decoration

Regarding the node type variable, the decoration 4 has twelve (12) nodes, has nine (9) common nodes with the decoration 2, and has four (4) common nodes with the decoration 3. This matrix can be used for further clustering analysis

To compare graphically the decorations 2, 3 and 4 on the type variable:

  • first: type variable is pasted to the list_compar() function

  • then: the plot is made with the plot_compar() function

dec.to.compare <- c(2, 3, 4)
imgs_path <- paste0(dataDir, "/imgs.tsv")
nodes_path <- paste0(dataDir, "/nodes.tsv")
edges_path <- paste0(dataDir, "/edges.tsv")
imgs <- read.table(imgs_path, sep="\t", stringsAsFactors = FALSE)
nodes <- read.table(nodes_path, sep="\t", stringsAsFactors = FALSE)
edges <- read.table(edges_path, sep="\t", stringsAsFactors = FALSE)
lgrph <- list_dec(imgs, nodes, edges)
g.compar <- list_compar(lgrph, nd.var = "type")
nds_compar <- plot_compar(listg = g.compar, 
                          graph2 = dec.to.compare,
                          focus = "nodes",
                          nd.size = c(0.5, 1.5),
                          doss = dataDir)
knitr::include_graphics(nds_compar) 

 

The function creates an image for each pair of stelae contained in the dec.to.compare variable (2, 3, 4), with a focus on nodes (focus = "nodes"). That is to say \(\frac{n!}{(n-2)!2!}\) pairwise comparisons, when n is the number of compared decorations. Then, to compare each pair of the five (5) decorations in the training dataset, there are 10 possible comparisons.

Edge comparisons

A less-common study in archaeological research is to count the common edges between pairs of decorations. The same_elements() function with an edge focus (focus = "edges") and considering the type of the nodes

imgs_path <- paste0(dataDir, "/imgs.tsv")
nodes_path <- paste0(dataDir, "/nodes.tsv")
edges_path <- paste0(dataDir, "/edges.tsv")
imgs <- read.table(imgs_path, sep="\t", stringsAsFactors = FALSE)
nodes <- read.table(nodes_path, sep="\t", stringsAsFactors = FALSE)
edges <- read.table(edges_path, sep="\t", stringsAsFactors = FALSE)
lgrph <- list_dec(imgs, nodes, edges)
df.same_edges <- same_elements(lgrph, nd.var = "type", focus = "edges")
diag(df.same_edges) <- cell_spec(diag(df.same_edges),
                                 font_size = 9)
knitr::kable(df.same_edges, row.names = TRUE, escape = F, table.attr = "style='width:30%;'",
             caption = "count of common edges between decorations") %>%
  column_spec(1, bold=TRUE) %>%
  kableExtra::kable_styling(position = "center", font_size = 12)
count of common edges between decorations
1 2 3 4 5
1 8 0 1 2 0
2 0 15 3 7 1
3 1 3 10 1 2
4 2 7 1 14 1
5 0 1 2 1 4

 

In this dataframe:

  • cells show the total number of common edges by decoration

  • the diagonal of the dataframe shows is the total number of edges of a given decoration

Here, the decoration 2 has fifteen (15) edges and shares three (3) common edges with the decoration 3. To show them, and the decoration 4, we use the list_compar() function on the same variable (type) and the plot_compar() function with an edge focus (focus = "edges").

dec.to.compare <- c(2, 3, 4)
imgs_path <- paste0(dataDir, "/imgs.tsv")
nodes_path <- paste0(dataDir, "/nodes.tsv")
edges_path <- paste0(dataDir, "/edges.tsv")
imgs <- read.table(imgs_path, sep="\t", stringsAsFactors = FALSE)
nodes <- read.table(nodes_path, sep="\t", stringsAsFactors = FALSE)
edges <- read.table(edges_path, sep="\t", stringsAsFactors = FALSE)
lgrph <- list_dec(imgs, nodes, edges)
g.compar <- list_compar(lgrph, nd.var = "type")
eds_compar <- plot_compar(listg = g.compar, 
                          graph2 = dec.to.compare,
                          focus = "edges",
                          nd.size = c(0.5, 1.7),
                          doss = dataDir)
knitr::include_graphics(eds_compar) 

This matrix can be used for further clustering analysis

Contemporaneous elements

At times some nodes are non-contemporaneous with one another, like on the Ibahernando stelae. This stelae was reused as a funerary stelae during Roman times with the addition of a Latin insciption “Alloquiu protaeidi.f hece. stitus”: Alluquio, son of Protacido, lies here (Basch 1966).

GIS view. The Ibahernando stelae (decoration 5)

GIS view. The Ibahernando stelae (decoration 5)

The writing (ecriture, node 1) has been carved over a spear (lance, node 2) and overlaps partially a V-notched shield (bouclier, node 3). The edges between node 1 and node 2, and the edge between node 1 and node 3, are diachronic edges

nds.df <- read_nds(site = "Ibahernando", 
                   decor = "Ibahernando",
                   doss = dataDir)
eds.df <- read_eds(site = "Ibahernando",
                   decor = "Ibahernando",
                   doss = dataDir)
img.graph <- plot_dec_grph(nodes = nds.df,
                           edges = eds.df,
                           site = "Ibahernando",
                           decor = "Ibahernando",
                           lbl.size = 2,
                           doss = dataDir)
magick::image_read(img.graph)
Ibahernando stelae (decoration 5) with only *normal* edges, node 1  overlaps node 2 and node 3

Ibahernando stelae (decoration 5) with only normal edges, node 1 overlaps node 2 and node 3

 

In this case, the non-contemporaneous layers of decoration, both for nodes and edges, should be removed before the comparison process. To do so, the original graph (1-component) will be split into different sub-graphs (n-component) by selecting > edges (see diachronic edges). The studied graph component will be retrieved with the component membership of a selected node.

To study only the Late Bronze Age iconographic layer of the Ibahernando steale, we can choose the Late Bronze Age node 4, the image of a sword (epee) dated to the middle and final stages of Late Bronze Age (ca 1250-950 BC). This node is believed to be contemporaneous with the spear (lance, node 2) and the shield (bouclier, node 3) so these three nodes are linked with normal edges. We pass the node 4 to the parameters of the contemp_nds() function:

selected.nd <- 4
nds.df <- read_nds(site = "Ibahernando", 
                   decor = "Ibahernando",
                   doss = dataDir)
eds.df <- read_eds(site = "Ibahernando",
                   decor = "Ibahernando",
                   doss = dataDir)
l_dec_df <- contemp_nds(nds.df, eds.df, selected.nd)
Ibahernando <- plot_dec_grph(nodes = nds.df,
                             edges = eds.df,
                             site = "Ibahernando",
                             decor = "Ibahernando",
                             nd.var = "type",
                             lbl.color = "brown",
                             lbl.size = 2.2,
                             doss = dataDir)
Ibahernando.img <- magick::image_read(Ibahernando)
Ibahernando.contemp <- plot_dec_grph(nodes = l_dec_df[[1]],
                                     edges = l_dec_df[[2]],
                                     site = "Ibahernando",
                                     decor = "Ibahernando",
                                     nd.var = "type",
                                     lbl.color = "brown",
                                     lbl.size = 2.2,
                                     doss = dataDir)
Ibahernando.contemp.img <- magick::image_read(Ibahernando.contemp)
magick::image_append(c(Ibahernando.img, Ibahernando.contemp.img))
Ibahernando stelae before and after the selection of node 4 (sword) graph component

Ibahernando stelae before and after the selection of node 4 (sword) graph component

 

Epigraphers, on the other hand, will want to study only the iconographic layer with the Latin writing. By selecting node 1 (ecriture), only the graph component of this node will be retained:

selected.nd <- 1
nds.df <- read_nds(site = "Ibahernando", 
                   decor = "Ibahernando",
                   doss = dataDir)
eds.df <- read_eds(site = "Ibahernando",
                   decor = "Ibahernando",
                   doss = dataDir)
l_dec_df <- contemp_nds(nds.df, eds.df, selected.nd)
img.graph <- plot_dec_grph(nodes = l_dec_df[[1]],
                           edges = l_dec_df[[2]],
                           site = "Ibahernando",
                           decor = "Ibahernando",
                           nd.var = "type",
                           lbl.size = 2,
                           lbl.color = "brown",
                           doss = dataDir)
magick::image_read(img.graph)
Ibahernando stelae after the selection of node 1 (writing) graph component

Ibahernando stelae after the selection of node 1 (writing) graph component

 

Summary

The flexibility of graph theory and tools available for the GIS database make the iconr package useful in managing, plotting and comparing (potentially large) sets of iconographic content.

Classify decorations

Besides graphical functions allowing one to highlight common elements (nodes and edges) between decorations, the package also allows one to prepare data for unsupervised classification, such as hierarchical clustering with the dist() and hclust():

par(mfrow=c(1, 2))
df.same_edges <- same_elements(lgrph, "type", "edges")
df.same_nodes<- same_elements(lgrph, "type", "nodes")
dist.nodes <- dist(as.matrix(df.same_nodes), method = "euclidean")
dist.edges <- dist(as.matrix(df.same_edges), method = "euclidean")
hc.nds <- hclust(dist.nodes, method = "ward.D")
hc.eds <- hclust(dist.edges, method = "ward.D") 
plot(hc.nds, main = "common nodes", cex = .8)
plot(hc.eds, main = "common edges", cex = .8)

 

Clustering of decorations on common nodes and clustering on common edges can be directly compared to one another:

suppressPackageStartupMessages(library(dendextend))
suppressPackageStartupMessages(library(dplyr))
par(mfrow=c(1, 2))
dend.nds <- as.dendrogram(hc.nds)
dend.eds <- as.dendrogram(hc.eds)
dendlist(dend.nds, dend.eds) %>%
  untangle(method = "step1side") %>% 
  tanglegram(columns_width = c(6, 1, 6),
             main_left = "common nodes",
             main_right = "common edges",
             lab.cex = 1.3,
             cex_main = 1.5,
             highlight_branches_lwd = F) 

 

In both clusterings, the Brozas stelae (decoration 3) and the Ibahernando stelae (decoration 5) are the ones having the most important proximities (ie the least Euclidian distance)

dec.to.compare <- c(3, 5)
g.compar <- list_compar(lgrph, nd.var = "type")
nds_compar <- plot_compar(listg = g.compar, 
                          graph2 = dec.to.compare,
                          focus = "nodes",
                          nd.size = c(0.5, 1.7),
                          doss = dataDir)
eds_compar <- plot_compar(listg = g.compar, 
                          graph2 = dec.to.compare,
                          focus = "edges",
                          nd.size = c(0.5, 1.7),
                          doss = dataDir)
knitr::include_graphics(nds_compar)

knitr::include_graphics(eds_compar) 

Here, the comparisons have been made on the basis of different types (ie, graphical units, GUs) with the variable type (nd.var = "type"). However, if a new column is added to the node dataframe or shapefile, the study can also incorporate the technique by which the GU was created (nd.var = "technique") or any other categorical variable. For example, two GUs on the Brozas steale were made with incisions (g_inc): the fibula (fibula de codo tipo Huelva, ca 1050-950 BC) and the comb

GIS view. The Brozas stelae (decoration 1)

GIS view. The Brozas stelae (decoration 1)

Nodes tree

Graph theory allows one to construct tree structures for categorical variables (eg. the different types of GUs). These structures allow generalization processes (up to the parent level) and specification processes (down to the child level). For example, a sword and a spear belong both to the weapons group (sub-group offensive weapons), a shield belongs to the weapons group (sub-group defensive weapons), etc.:

par(mar=c(0,0,0,0))
g <- graph_from_literal(objects-+weapons,
                        objects-+personnal_item,
                        weapons-+offensive_weapons,
                        weapons-+defensive_weapons,
                        offensive_weapons-+spear,
                        offensive_weapons-+sword,
                        defensive_weapons-+shield,
                        defensive_weapons-+helmet,
                        personnal_item-+miror,
                        personnal_item-+comb)
layout <- layout.reingold.tilford(g)
plot(g,
     layout = layout,
     vertex.color = "white",
     vertex.frame.color = "white",
     vertex.size = 20,
     vertex.label.cex = 0.8,
     vertex.label.color = "black",
     vertex.label.family = "sans",
     edge.arrow.size = 0.5
     )

Such a formalism can be used to weight the differences between nodes, to conduct analysis with different levels of precision or to overcome issues of idiosynchratic typologies.

Using the diachronic edge notation, tree structure can also be used to construct relative chronology diagrams, similar to a Harris matrix. For example, with the data.tree package and the Ibahernado stelae (decoration 5):

library(data.tree)
lgrph <- list_dec(imgs, nodes, edges)
edges.iba <- igraph::as_data_frame(lgrph[[5]], what="edges")
overlap.nodes <- unlist(unique(edges.iba[edges.iba$type == ">", "from"]))
overlap.nodes <- unique(as.character(overlap.nodes))
contemp.nodes <- unlist(unique(edges.iba[edges.iba$type == "=", c("from","to")]))
contemp.nodes <- unique(as.character(contemp.nodes))
df.stratig <- data.frame(over = rep(overlap.nodes, length(contemp.nodes)),
                            under = contemp.nodes)

df.stratig$pathString <- paste(lgrph[[5]]$decor,
                               df.stratig$over, 
                               df.stratig$under, 
                               sep = "/")
superpo <- as.Node(df.stratig)
print(superpo)
>     levelName
> 1 Ibahernando
> 2  °--1      
> 3      ¦--2  
> 4      ¦--3  
> 5      °--4

References

Alexander, Craig. 2008. “The Bedolina Map – an Exploratory Network Analysis.” In Layers of Perception. Proceedings of the 35th International Conference on Computer Applications and Quantitative Methods in Archaeology (Caa), Berlin, 2.-6. April 2007, edited by A. Posluschny, K. Lambers, and I. Herzog, 366–71. Koll. Vor- u. Frühgesch. https://doi.org/https://doi.org/10.11588/propylaeumdok.00000512.

Basch, Martı́n Almagro. 1966. Las Estelas Decoradas Del Suroeste Peninsular. Vol. 8. Editorial CSIC-CSIC Press.

Dı́az-Guardamino Uribe, Marta. 2010. “Las Estelas Decoradas En La Prehistoria de La Penı́nsula Ibérica.” PhD thesis, Universidad Complutense de Madrid, Servicio de Publicaciones. https://eprints.ucm.es/11070/1/T32200.pdf.

Huet, Thomas. 2018. “Geometric Graphs to Study Ceramic Decoration.” In Exploring Oceans of Data, Proceedings of the 44th Conference on Computer Applications and Quantitative Methods in Archaeology, Caa 2016, edited by Mieko Matsumoto and Espen Uleberg, 311–24. Archaeopress. https://hal.archives-ouvertes.fr/hal-02913656.

Huet, Thomas, and Craig Alexander. 2015. “Méthodes Informatiques Pour L’étude Des Gravures Rupestres : Les Exemples Du Valcamonica (Italie) et Du Mont Bego (France).” In Recherches Sur L’âge Du Bronze, Nouvelles Approches et Perspectives, Actes de La Journée d’étude de L’Association Pour La Promotion Des Recherches Archéologiques Sur L’âge Du Bronze, 28 Février 2014, Saint-Germain-En-Laye, Bulletin de L’APRAB, Suppl. N° 1, edited by M. Nordez, L. Rousseau, and M. Cervel, 15–29. Nantes. https://www.researchgate.net/publication/347437308_Methodes_informatiques_pour_l'etude_des_gravures_rupestres_les_exemples_du_Valcamonica_Italie_et_du_mont_Bego_France.